/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 *  Copyright 2008 Global Sign In
 *
 *  This code is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <sstream>

#include "OTLBase.h"

using std::clog;
using std::endl;
using std::string;
using std::stringstream;
using std::vector;

OTLRow::OTLRow(otl_read_iter &iter,
	int columnsCount, int rowNum) :
	m_iter(iter),
	m_columnsCount(columnsCount),
	m_rowNum(rowNum)
{
}

OTLRow::~OTLRow()
{
}

int OTLRow::getColumnsCount(void) const
{
	return m_columnsCount;
}

string OTLRow::getColumn(int nColumn) const
{
	string str;

	try
	{
		m_iter.get(nColumn, str);
	}
	catch(otl_exception &ex)
	{
		clog << "OTL error: " << ex.msg << endl;
	}

	return str;
}

OTLResults::OTLResults() :
	m_columnsCount(0),
	m_currentRow(0)
{
	m_stream.set_lob_stream_mode(true);
	m_iter.attach(m_stream);
}

OTLResults::~OTLResults()
{
}

otl_stream &OTLResults::getStream(void)
{
	return m_stream;
}

unsigned long OTLResults::getRowsCount(void)
{
	return (unsigned long)m_stream.get_rpc();
}

bool OTLResults::hasMoreRows(void)
{
	// Is the stream at EOF ?
	if (m_stream.eof())
	{
		return false;
	}

	return true;
}

string OTLResults::getColumnName(unsigned int nColumn)
{
	otl_var_desc *pVarDesc = NULL;
	int descLength = 0;

	try
	{
		pVarDesc = m_stream.describe_out_vars(descLength);
	}
	catch(otl_exception &ex)
	{
		clog << "OTL error: " << ex.msg << endl;
	}

	if ((pVarDesc == NULL) ||
		((int)nColumn >= descLength))
	{
		return "";
	}

	if (pVarDesc[nColumn].name[0] != '\0')
	{
		return pVarDesc[nColumn].name;
	}

	return "";
}

OTLRow *OTLResults::nextRow(void)
{
	if (m_columnsCount == 0)
	{
		// Get the number of columns
		try
		{
			otl_var_desc *pVarDesc = m_stream.describe_out_vars(m_columnsCount);
		}
		catch(otl_exception &ex)
		{
			clog << "OTL error: " << ex.msg << endl;
		}
	}

	// The stream and iterator start up at row 0
	if (m_currentRow > 0)
	{
		// Move to the next row
		if (!m_iter.next_row())
		{
			return NULL;
		}
	}

	// FIXME: this better be used right away or will also be "moved"
	// forward by the next call to nextRow()
	OTLRow *pRow = new OTLRow(m_iter, m_columnsCount, m_currentRow);

	++m_currentRow;

	return pRow;
}

bool OTLResults::rewind(void)
{
	// Rewind the stream
	m_stream.rewind();
	m_currentRow = 0;

	return true;
}

OTLBase::OTLBase(const string &hostName, const string &databaseName,
	const string &userName, const string &password) :
	m_hostName(hostName),
	m_databaseName(databaseName),
	m_userName(userName),
	m_password(password),
	m_isOpen(false)
{
	open();
}

OTLBase::~OTLBase()
{
	close();
}

bool OTLBase::isOpen(void) const
{
	return m_isOpen;
}

void OTLBase::open(void)
{
	string connectStr(m_userName + "/" + m_password);

	// Initialize
	otl_connect::otl_initialize();

	try
	{
		// Connect to the database
		m_database.rlogon(connectStr.c_str());
		m_isOpen = true;
	}
	catch(otl_exception &ex)
	{
		clog << "OTL error: " << ex.msg << endl;
	}
}

void OTLBase::close(void)
{
	m_isOpen = false;
	m_database.logoff();
}

string OTLBase::getUniversalUniqueId(void)
{
	string uuid;

	// FIXME: do this
	return uuid;
}

string OTLBase::escapeString(const string &text)
{
	// FIXME: no need for string escaping ?
	return text;
}

bool OTLBase::executeSimpleStatement(const string &sql, string &insertAutoId)
{
	bool success = false;

	if ((sql.empty() == true) ||
		(m_isOpen == false))
	{
		return false;
	}

	insertAutoId.clear();

	try
	{
		otl_stream inputStream(50,
			sql.c_str(),
			m_database);	
		inputStream.set_lob_stream_mode(true);

		while (!inputStream.eof());

		success = true;
	}
	catch(otl_exception &ex)
	{
		clog << "OTL error: " << ex.msg << endl;
	}

	// FIXME: any way to get the ID of the new row the statement may have created ?

	return success;
}

OTLResults *OTLBase::executeStatement(const char *sqlFormat, ...)
{
	char stringBuff[2048];
	va_list ap;

	if ((sqlFormat == NULL) ||
		(m_isOpen == false))
	{
		return NULL;
	}

	va_start(ap, sqlFormat);
	int numChars = vsnprintf(stringBuff, 2048, sqlFormat, ap);
	if (numChars <= 0)
	{
#ifdef DEBUG
		clog << "OTLBase::executeStatement: couldn't format statement" << endl;
#endif
		return NULL;
	}
	if (numChars >= 2048)
	{
		// Not enough space
#ifdef DEBUG
		clog << "OTLBase::executeStatement: not enough space (" << numChars << ")" << endl;
#endif
		return NULL;
	}
	stringBuff[numChars] = '\0';
	va_end(ap);

	OTLResults *pResults = new OTLResults();
	try
	{
		otl_stream &inpuStream = pResults->getStream();

		inpuStream.open(50,
			stringBuff,
			m_database);	
	}
	catch(otl_exception &ex)
	{
		clog << "OTL error: " << ex.msg << endl;

		delete pResults;
		pResults = NULL;
	}

	return pResults;
}

OTLResults *OTLBase::executeStatement(const string &sqlFormat,
	unsigned long min, unsigned long max)
{
	if (sqlFormat.empty() == true)
	{
		return NULL;
	}

	stringstream paginationStatement;
	paginationStatement << "SELECT * FROM (SELECT NS.*, ROWNUM rnum FROM (";
	paginationStatement << sqlFormat;
	paginationStatement << ") NS WHERE ROWNUM <= ";
	paginationStatement << max;
	paginationStatement << ") WHERE rnum >= ";
	paginationStatement << min;
	paginationStatement << ";";

	// Call overload
	return executeStatement(paginationStatement.str().c_str());
}

